package com.homestead.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.homestead.bo.AdaptOrgBo;
import com.homestead.bo.AddOrgBo;
import com.homestead.constant.RedisCacheKeyConstant;
import com.homestead.constant.RedisLockKeyConstant;
import com.homestead.domain.dos.CouponThemeCache;
import com.homestead.domain.entity.CouponThemeAdaptOrg;
import com.homestead.domain.mapper.CouponThemeAdaptOrgMapper;
import com.homestead.service.CouponApplicableOrgService;
import com.homestead.service.CouponThemeCacheService;
import com.homestead.service.CouponThemeService;
import com.homestead.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.redisson.api.RSet;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author HanBin_Yang
 * @since 2021/5/30 9:58
 */
@Service
@Slf4j
public class CouponApplicableOrgServiceImpl extends ServiceImpl<CouponThemeAdaptOrgMapper, CouponThemeAdaptOrg> implements CouponApplicableOrgService {
    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private CouponThemeService couponThemeService;

    @Autowired
    private CouponThemeCacheService couponThemeCacheService;


    @Override
    public boolean add(AddOrgBo bo) {
        // 准备adaptOrg bean
        List<CouponThemeAdaptOrg> adaptOrgList = prepareApplicableOrgBean(bo);
        int countResult = baseMapper.insertBatch(adaptOrgList);
        if (countResult != bo.getAdaptOrgList().size()) {
            throw new RuntimeException("yichang");
        }
        addApplicableOrgIdsCache(bo.getCouponThemeId(), adaptOrgList);

        return true;
    }

    @Override
    public Set<Long> getApplicableOrgIds(Long couponThemeId) {
        RSet<Long> rSet = getAdaptOrgIdsRSet(couponThemeId);
        Set<Long> orgIds = rSet.readAll();
        if (CollectionUtils.isEmpty(orgIds)) {
            // 在布隆过滤器中，直接返回null
            if (couponThemeCacheService.couponThemeIdIsMarkedAsNotExist(couponThemeId)) {
                log.error("getAdaptOrgIds券活动id不存在，couponThemeId={}", couponThemeId);
                return null;
            }
            String lockKeyName = RedisLockKeyConstant.SYNC_ADAPT_ORG_IDS_TO_CACHE + couponThemeId;
            return RedisUtil.executeLock(lockKeyName, 60, TimeUnit.SECONDS, () -> {
                Set<Long> ids = rSet.readAll();
                if (CollectionUtils.isEmpty(ids)) {
                    return rebuildApplicableOrgIdsCache(couponThemeId, rSet);
                }
                return ids;
            });
        }
        return orgIds;
    }

    private Set<Long> rebuildApplicableOrgIdsCache(Long couponThemeId, RSet<Long> rSet) {
        // 在布隆过滤器中，直接返回null
        if (couponThemeCacheService.couponThemeIdIsMarkedAsNotExist(couponThemeId)) {
            log.error("getAdaptOrgIds券活动id在布隆过滤器中，couponThemeId={}", couponThemeId);
            return null;
        }
        log.info("adaptOrgIds即将重建缓存: couponThemeId={}", couponThemeId);
        LambdaQueryWrapper<CouponThemeAdaptOrg> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper
                .select(CouponThemeAdaptOrg::getOrgId)
                .eq(CouponThemeAdaptOrg::getCouponThemeId, couponThemeId);
        List<Object> orgIds = baseMapper.selectObjs(queryWrapper);
        if (CollectionUtils.isEmpty(orgIds)) {
            log.error("adaptOrgIds重建缓存,数据库中查询适用组织null, couponThemeId={}", couponThemeId);
            rSet.addAll(Collections.emptySet());
            return null;
        }
        Set<Long> ids = doAddOrgIdsCache(couponThemeId, orgIds, rSet);
        log.info("adaptOrgIds重建缓存ok: couponThemeId={}, adaptIds={}", couponThemeId, JSON.toJSONString(ids));
        return ids;
    }

    private Set<Long> doAddOrgIdsCache(Long couponThemeId, List<Object> orgIds, RSet<Long> rSet) {
        Objects.requireNonNull(couponThemeId);
        Set<Long> ids = orgIds.stream().map(bean -> Long.parseLong(String.valueOf(bean))).collect(Collectors.toSet());
        rSet.addAll(ids);
        return ids;
    }


    private RSet<Long> getAdaptOrgIdsRSet(Long couponThemeId) {
        CouponThemeCache themeCache = couponThemeCacheService.getSelectedFieldsById(couponThemeId, Collections.singleton(CouponThemeCache.FIELDS.COUPON_TYPE));
        String keyName = RedisCacheKeyConstant.COUPON_THEME_ADAPT_ORG_IDS + couponThemeId + themeCache.getCouponType();
        return redissonClient.getSet(keyName);
    }

    /**
     * 添加适用组织ids缓存
     * @param couponThemeId
     * @param adaptOrgList
     */
    private void addApplicableOrgIdsCache(Long couponThemeId, List<CouponThemeAdaptOrg> adaptOrgList) {
        Set<Long> adaptOrgCacheSet = prepareApplicableOrgCacheBean(adaptOrgList);
        RSet<Long> rSet = getAdaptOrgIdsRSet(couponThemeId);
        rSet.addAll(adaptOrgCacheSet);
        CouponThemeCache couponThemeCache = couponThemeCacheService.getSelectedFieldsById(couponThemeId, Collections.singleton(CouponThemeCache.FIELDS.END_TIME));
        Date endTime = couponThemeCache.getEndTime();
        long expireSeconds = RedisUtil.specifiedDateExpireSeconds(endTime) + 60L;
        rSet.expire(expireSeconds, TimeUnit.SECONDS);
    }

    private Set<Long> prepareApplicableOrgCacheBean(List<CouponThemeAdaptOrg> couponThemeAdaptOrgList) {
        return couponThemeAdaptOrgList.stream().map(CouponThemeAdaptOrg::getOrgId).collect(Collectors.toSet());
    }

    private List<CouponThemeAdaptOrg> prepareApplicableOrgBean(AddOrgBo bo) {

        List<AdaptOrgBo> adaptOrgBoList = bo.getAdaptOrgList();

        Queue<Long> ids = RedisUtil.generateIds(adaptOrgBoList.size());

        List<CouponThemeAdaptOrg> list = new ArrayList<>();
        for (int i = 0; i < adaptOrgBoList.size(); i++) {
            AdaptOrgBo adaptOrgBo = adaptOrgBoList.get(i);
            CouponThemeAdaptOrg org = new CouponThemeAdaptOrg();
            BeanUtil.copyProperties(adaptOrgBo, adaptOrgBo);
            org.setId(ids.poll());
            list.add(org);
        }

        return list;
    }
}
